package pso.mcpso;

import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import pso.PSOAlgorithm;
import pso.particle.Particle;
import pso.particle.ParticleNeighborhood;
import pso.position.PositionUpdateStrategy;
import pso.velocity.VelocityUpdateStrategy;
import evolution.algorithm.Iterative;
import evolution.problem.OptimizationProblem;
import evolution.stop.StopCondition;

public class MCPSOAlgorithm extends PSOEpochRunner implements PSOAlgorithm {

	private List<PSOEpochRunner> slaves;
	private ExecutorService threadPool;
	private double[] bestSlavePosition;
	private double bestSlaveValue;
	private PSOAlgorithm masterAlgorithm;

	public MCPSOAlgorithm(PSOAlgorithm masterAlgorithm, int epochLength) {
		super(null, epochLength);
		setPSOAlgorithm(this);
		this.masterAlgorithm = masterAlgorithm;
	}

	@Override
	public void run() {
		List<PSOEpochRunner> tasks = prepareTasksList();
		int populations = tasks.size();
		threadPool = Executors.newFixedThreadPool(populations);

		while (!getStopCondition().shouldStop(this)) {
			try {
				threadPool.invokeAll(tasks);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			collectResultsFromSlaves();
		}
		threadPool.shutdown();
	}

	private List<PSOEpochRunner> prepareTasksList() {
		ArrayList<PSOEpochRunner> tasks = new ArrayList<PSOEpochRunner>(slaves);
		tasks.add(this);
		return tasks;
	}

	private void collectResultsFromSlaves() {
		for (int i = 0; i < slaves.size(); i++) {
			ParticleNeighborhood neighborhood = slaves.get(i).getPSOAlgorithm()
					.getSwarm()[0].getNeighborhood();
			if (neighborhood.getBestValue() > bestSlaveValue) {
				bestSlaveValue = neighborhood.getBestValue();
				bestSlavePosition = neighborhood.getBestPosition();
			}
		}
	}

	public List<PSOEpochRunner> getSlaves() {
		return slaves;
	}

	public void setSlaves(List<PSOEpochRunner> slaves) {
		this.slaves = slaves;
	}

	@Override
	public void runIteration() {
		masterAlgorithm.runIteration();
	}

	@Override
	public int getCurrentIteration() {
		return masterAlgorithm.getCurrentIteration();
	}

	@Override
	public Particle[] getSwarm() {
		return masterAlgorithm.getSwarm();
	}

	@Override
	public void setSwarm(Particle[] swarm) {
		masterAlgorithm.setSwarm(swarm);
	}

	@Override
	public VelocityUpdateStrategy getVelocityUpdateStrategy() {
		return masterAlgorithm.getVelocityUpdateStrategy();
	}

	@Override
	public void setVelocityUpdateStrategy(
			VelocityUpdateStrategy velocityUpdateStrategy) {
		masterAlgorithm.setVelocityUpdateStrategy(velocityUpdateStrategy);
	}

	@Override
	public PositionUpdateStrategy getPositionUpdateStrategy() {
		return masterAlgorithm.getPositionUpdateStrategy();
	}

	@Override
	public void setPositionUpdateStrategy(
			PositionUpdateStrategy positionUpdateStrategy) {
		masterAlgorithm.setPositionUpdateStrategy(positionUpdateStrategy);
	}

	@Override
	public StopCondition<Iterative> getStopCondition() {
		return masterAlgorithm.getStopCondition();
	}

	@Override
	public void setStopCondition(StopCondition<Iterative> stopCondition) {
		masterAlgorithm.setStopCondition(stopCondition);
	}

	public double[] getBestSlavePosition() {
		return bestSlavePosition;
	}

	public void setBestSlavePosition(double[] bestSlavePosition) {
		this.bestSlavePosition = bestSlavePosition;
	}

	public double getBestSlaveValue() {
		return bestSlaveValue;
	}

	public void setBestSlaveValue(double bestSlaveValue) {
		this.bestSlaveValue = bestSlaveValue;
	}

	@Override
	public void addPropertyChangeListener(String property,
			PropertyChangeListener listener) {
		masterAlgorithm.addPropertyChangeListener(property, listener);
	}

	@Override
	public void removePropertyChangeListener(String property,
			PropertyChangeListener listener) {
		masterAlgorithm.removePropertyChangeListener(property, listener);
	}

	@Override
	public OptimizationProblem getOptimizationProblem() {
		return masterAlgorithm.getOptimizationProblem();
	}
}
